#!/usr/bin/python3
# -*- coding: UTF-8 -*- 
# 设置utf-8  显示中文
"""
@Create Time: 2021/3/12 23:48
@Author: guo
@File：generate_yaml.py
"""
import json

import mitmproxy
from mitmproxy import http
import yaml
import copy
import re
import sys
import os

sys.path.append("../")
from config.get_conf_data import GetConfData


class GenYamlTemplate:

    def __init__(self):
        self.__yaml_case_template_path = "case_yml_template.yml"
        self.__yaml_page_template_path = "page_yml_template.yml"

        # 设置目标host
        self.__host_target = ["192.168.9.105", "105.xxx.xxxx", "xxx.xxx.xxx"]
        # 静态资源
        self.__static_ext = ["js", "css", "ico", "jpg", "png", "gif", "jpeg", "bmp", "ttf"]
        # 静态文件,在家校pc端查看,其中 "application/javascript" js 文件也是无效的。
        self.__static_files = ["text/css", "image/jpeg", "image/png", "image/gif", "text/html",
                               "application/octet-stream",
                               "application/x-protobuf"]
        # 图片
        self.__media_types = ["image", "video", "audio"]
        # 上面的几种静态资源，需要通过resonse来进行校验。存在于 response.headers("Content-Type")中
        # 其中response为 flow.response

        # 本次由于只需要生成操作的页面的接口对应的yaml文件。所以需要拿 request.headers("Content-Type") 是否为None进行判断即可。
        self.__type = "Content-Type"
        # 环境
        self.__env_web = "web"
        self.__env_app = "app"
        self.__env = self.__env_web
        # 读取yaml模板
        self.__case_yaml_temp = yaml.safe_load(open(self.__yaml_case_template_path, encoding="utf8"))
        self.__page_yaml_temp = yaml.safe_load(open(self.__yaml_page_template_path, encoding="utf-8"))
        # remove 字符串。因为公司的有些remove接口 的request 的headers里没有Content-Type。 有些是有值。
        self.__remove_str = "remove"
        self.__post_str = "post"
        self.__get_str = "get"
        self.__json_str = "json"
        self.__content_json = "application/json"
        self.__data_str = "data"
        self.__params_str = "params"
        self.__case_tmp_str = "case_template"
        self.__page_tmp_str = "page_template"
        self.__web_str = "web"
        self.__zhjx_gate = "zhjxgate"
        self.__web_flag = f"/{self.__web_str}/"
        self.__zhjx_flag= f"/{self.__zhjx_gate}/"
        self.__env_web_flag = "web"
        self.__env_app_flag = "app"
        # 环境切换
        self.__env_flag = self.__env_web_flag
        self.__conf = GetConfData()
        self.__yaml_dir = self.__conf.get_ymlfile_abspath()
        self.__init_key()
        self.__count=0
    def __init_key(self):
        """需要用到的一些key,先不考虑把这些key写入到配置文件或独立出去."""
        self.__app_login_key="app_login"
        self.__web_login_key = "web_login"
        self.__web_str_key = "web_str"
        self.__app_str_key = "app_str"
        self.__test_init_key = "test_init"
        self.__login_type_key = "login_type"
        self.__page_template_key = "page_template"
        self.__case_template_key = "case_template"
        self.__login_data_key = "login_data"
        self.__api_data_key = "api_data"
        self.__path_key = "path"
        self.__data_type_key = "data_type"
        self.__request_data_key = "request_data"
        self.__method_key = "method"
        self.__timestamp_key = "_"


    def write_data_to_yaml(self, yaml_path: str, data: dict) -> None:
        yml_path = yaml_path
        # 先检查是否以.yaml 结尾，如果是统一修改为 .yml
        yaml_suffix=".yaml"
        yml_suffix=".yml"
        exp=f"({yaml_suffix})$"
        yml_path=re.sub(exp,yml_suffix,yml_path)

        # 如果不是以.yml结尾，就更改为 .yml
        if yml_path:
            yml_path = yml_path if yml_path.endswith(".yml")  else yml_path + ".yml"

        # 拼接路径
        target_path = os.path.join(self.__yaml_dir, yml_path)
        # 获取目录
        target_dir = os.path.dirname(target_path)
        # 当目录不存在时，则进行创建操作
        if not os.path.exists(target_dir):
            # 当已经存在的目录，则不创建
            os.makedirs(target_dir,exist_ok=True)


        datas={}
        # 检测文件是否存在
        if os.path.exists(target_path):
            data_tmp = yaml.safe_load(open(target_path,encoding="utf-8"))
            # 当不为None时，赋值给变量
            if data_tmp !=None:
                datas= data_tmp

        # 写入文件
        with open(target_path,"w",encoding="utf-8") as fp :
            datas.update(data)
            # 以原来的顺序写入到文件中，保存原有顺序不变，同时中文不能乱码。
            yaml.safe_dump(datas,fp,allow_unicode="utf-8",default_flow_style=False,sort_keys=False)


    def request(self, flow: mitmproxy.http.HTTPFlow):
        self.__count+=1
        request = flow.request
        host = request.host
        # 获取不带参数的path，返回的tuple类型
        path = request.path_components
        # 获取url path路径
        url_path = "/" + "/".join(path)
        # 用于获取，并校验是否为删除接口
        latest_path = path[-1]

        # 获取请求方法类型
        method = str.lower(request.method)
        # 获取提交的参数,可按字典的操作方式进行操作。
        # 在获取数据之前,要先判断是get请求还是post请求,两者存的位置不一样
        if method == self.__get_str:
            # 目前知道的,当为get请求时,请求的参数是存放于 request.query 中
            params = request.query
        # post 请求(非get请求)
        else:
            params = request.urlencoded_form

        # 获取各项值
        data={}
        for key ,item in params.items():
            # 有些字段是时间戳,可以不用录入,这个要根据各个公司的接口规范要求
            # 目前时间戳是非必填字段
            if self.__timestamp_key != key :
                data[key] = item


        # 获取请求的 Content-Type
        content_type = request.headers.get(self.__type)
        # 过滤请求，只保留目标数据
        if (host in self.__host_target) and \
                ((content_type != None) or ((content_type == None) and latest_path.startswith(self.__remove_str))):
            # 先拷贝yaml模板的数据,深度拷贝
            case_yaml_datas = copy.deepcopy(self.__case_yaml_temp)
            page_yaml_datas = copy.deepcopy(self.__page_yaml_temp)


            # 至于用什么标识进行切割,要根据实际的项目进行确定,找出共同点
            # 检测 "/web/" 字符串是否存在于path路径中
            if self.__web_flag in url_path:
                # 获取切割后的功能模块的路径.
                tmp_path = url_path.split(self.__web_flag)[-1]

            # 检测 "/zhjxgate/" 字符串是否存在于 path路径中
            elif self.__zhjx_flag in url_path:
                tmp_path = url_path.split(self.__zhjx_flag)[-1]
            # 暂时不考虑其他情况.
            else:
                pass

            # 获取当前的接口名称(如:add,remove,query,audit),在写入page 和 case层yml文件时,需要用到
            key = os.path.basename(tmp_path)
            page_key = key
            case_key = "test_"+key

            # 生成各自的key的数据,要使用deepcopy,防止出现引用现象.因为在使用浅拷贝时,只要是容器类型(字典,列表,元组,集合)时,
            # 使用copy.copy时,是直接引用的对象的id,也就是内存中的地址.所以一改,就全部都改了.并且最后在赋值的时候,也是进行引用赋值
            # 使用deepcopy 是相当于重新新建了一份数据,是完全独立的.
            case_templ_data = copy.deepcopy(case_yaml_datas[self.__case_template_key])
            page_templ_data = copy.deepcopy(page_yaml_datas[self.__page_template_key])

            # 先生成各自层的数据
            case_yaml_datas[case_key] = case_templ_data
            page_yaml_datas[page_key] = page_templ_data



            # 判断是web端 还是app端
            if self.__env_flag == self.__env_web_flag:
                # 下面进行web yaml模板的配置
                case_login_key = self.__web_login_key
                page_login_key = self.__web_login_key
                # 终端标识
                type_flag_key = self.__web_str_key

            elif self.__env_flag == self.__env_app_flag:
                case_login_key = self.__app_login_key
                page_login_key = self.__app_login_key
                type_flag_key = self.__app_str_key

            case_login = copy.deepcopy(case_yaml_datas[case_login_key])
            page_login = copy.deepcopy(page_yaml_datas[page_login_key])
            # 获取登录类型
            login_type = page_yaml_datas[type_flag_key]

            # 将case模板中的 test_init 下的login_type 和 login_data 的值全部替换掉
            case_yaml_datas[self.__test_init_key][self.__login_type_key] = login_type
            # 必须再次进行deepcopy,否则会以引用的方式出来.
            # 也就是说 deepcopy后的值,必须只能赋值一次,否则
            case_login_data = copy.deepcopy(case_yaml_datas[case_login_key])
            case_yaml_datas[self.__test_init_key][self.__login_data_key] = case_login_data

            # 获取各自api_data数据
            case_apidata = case_yaml_datas[case_key][self.__api_data_key]
            page_apidata = page_yaml_datas[page_key][self.__api_data_key]

            #替换page层的path
            page_apidata[self.__path_key] = url_path
            data_type=""
            # 下面开始进行method判断
            if method == self.__get_str :
                # 当为get请求时，data_type 为：params
                data_type = self.__params_str

            elif method == self.__post_str:
                # 当为post请求时，要判断content-type 是否为application/json，若为，则表示为 data_type=json
                if self.__content_json in content_type:
                    data_type = self.__json_str
                else:
                    data_type = self.__data_str
            # 当为其他类型的请求方法时，暂时不考虑，或将设置为data
            else:
                data_type = self.__data_str

            # 对各自的data_type 进行赋值
            case_apidata[self.__data_type_key] = data_type
            page_apidata[self.__data_type_key] = data_type
            # 对page层的request_data下的method 进行赋值
            page_apidata[self.__request_data_key][self.__method_key] = method
            # 再对各自的request_data中的data 进行赋值 ,注意顺序
            request_data = {}
            request_data[data_type] = data
            case_apidata[self.__request_data_key] = request_data
            page_apidata[self.__request_data_key][data_type] = data
            # 对各login_data 下的 login_data 进行赋值
            case_apidata[self.__login_data_key][self.__login_data_key] = case_login
            page_apidata[self.__login_data_key][self.__login_data_key] = page_login
            # 对page层下的 login_data下的login_type 进行赋值
            page_apidata[self.__login_data_key][self.__login_type_key] = login_type

            #******************** 下面 开始进行 yml 文件的生成了 ××××××××××××××××××××××
            '''
            在进行yaml文件生成时，需要注意的是： 一个功能模块下的接口，应该在同个yaml文件中，且各自接口的名称(如add，query）作为各自的key
            目前在进行分割时，是优先拿web进行侵害，当不存在web时，再拿zhjxgate进行分割。
            需要注意的是，app的接口，ymlfile目录下的 app目录下，web接口在ymlfile目录下的 web目录下。
            '''

            # 获取路径
            # 这块的写法是不严谨的,有可能会出现 key为 空的情况.即 /web/action,这种情况,此时的 tmp_path = "action"
            # 当这情况时,此时的dir为空,也就是""
            dir = os.path.dirname(tmp_path)
            if dir == "":
                dir = os.path.basename(tmp_path)
            # 获取文件名称
            filename = os.path.basename(dir)+".yml"

            # 设置case层和 page层各自的yml文件
            page_file = self.__web_str+"/"+dir+"/"+filename
            case_file = self.__web_str+"/"+dir+"/"+"test_"+filename


            self.write_data_to_yaml(case_file,case_yaml_datas)
            self.write_data_to_yaml(page_file,page_yaml_datas)


addons = [GenYamlTemplate()]
